﻿namespace ImageProcessing.Forms
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Windows.Forms.DataVisualization.Charting;
    using ImageProcessing.Draw_shaps.ShapesLib;
    using ShapesLib;

    /// <summary>
    /// Pixel Operations Graph Form
    /// </summary>
    public partial class PixelOperationsGraph : Form
    {
        /// <summary>
        /// the tolerance
        /// </summary>
        private const float Tolerance = 3f;

        /// <summary>
        /// If the user click ok
        /// </summary>
        private bool ok = false;
        
        /// <summary>
        /// If the user click ok
        /// </summary>
        private bool resized = false;

        /// <summary>
        /// Referemce to the image
        /// </summary>
        private DImage image;

        /// <summary>
        /// First Image .. don't Change used as Backup
        /// </summary>
        private ImageStructure[,] firstImage;

        /// <summary>
        /// First Image .. don't Change used as Backup
        /// </summary>
        private ImageStructure[,] firstOriginalImage;

        /// <summary>
        /// First Image .. don't Change used as Backup
        /// </summary>
        private ImageStructure[,] newImage;

        /// <summary>
        /// Histogram Object
        /// </summary>
        private Histogram histo = new Histogram();

        /// <summary>
        /// Gray Array hold Values .. red, green, blue arrays .. may be later
        /// </summary>
        private int[] grayArray;

        /// <summary>
        /// First Line 
        /// </summary>
        private Line l;

        /// <summary>
        /// Detect if the circle is moving 
        /// </summary>
        private bool moveCircle;
        
        /// <summary>
        /// the number of the first currnt line
        /// </summary>
        private int currl1;
       
        /// <summary>
        /// the number of the second currnt line
        /// </summary>
        private int currl2;
       
        /// <summary>
        /// List of the lines
        /// </summary>
        private List<Line> lines = new List<Line>();
       
        /// <summary>
        /// List of the Circles
        /// </summary>
        private List<Circle> circles = new List<Circle>();

        /// <summary>
        /// the number of current Circle moving
        /// </summary>
        private int currCircle;

        /// <summary>
        /// Initializes a new instance of the <see cref="PixelOperationsGraph"/> class.
        /// </summary>
        /// <param name="i">The image.</param>
        public PixelOperationsGraph(DImage i)
        {
            this.InitializeComponent();
            this.l = new Line();
            this.l.P1X = -5;
            this.l.P1Y = 260;
            this.l.P2X = 260;
            this.l.P2Y = -5;
            this.l.PenColor = Color.Black;
            this.l.PenWidth = 4;
            this.lines.Add(this.l);
            this.circles.Add(new Circle(this.l.P1X, this.l.P1Y));
            this.circles.Add(new Circle(this.l.P2X, this.l.P2Y));
            this.Pnl_Graph.Location = new Point(400, 30);
            this.Pnl_Graph.Size = new Size(260, 260);
            this.Pnl_Backpnl.Location = new Point(20, 20);
            this.Pnl_Backpnl.Size = new Size(275, 275);
            this.Pnl_Backpnl.SendToBack();
            this.grayArray = new int[256];
            this.image = i;
            this.firstOriginalImage = Operations.CopyImage(this.image.ImageStructArray, this.image.Width, this.image.Height);
            this.image.BackupImageArray();
            if (this.image.Width > this.Pcb_OrginalImage.Size.Width || this.image.Height > this.Pcb_OrginalImage.Size.Height)
            {
                this.resized = true;
                this.firstImage = ResizeImage.BicubicResize(this.Pcb_OrginalImage.Size.Width, this.Pcb_OrginalImage.Size.Height, this.firstOriginalImage, 3);
            }
            else
            {
                this.firstImage = this.firstOriginalImage;
            }

            this.Pcb_OrginalImage.Image = Operations.DisplayMatrix(this.firstImage);
            this.Pcb_ModifiedImage.Image = Operations.DisplayMatrix(this.firstImage);
            this.newImage = Operations.CopyImage(this.firstImage, this.firstImage.GetLength(1), this.firstImage.GetLength(0));
            
            // this.InitHistograms();
            this.Chrt_Original.Visible = false;
            this.Chrt_Modified.Visible = false;
        }

        /// <summary>
        /// Gets or sets the gray array.
        /// </summary>
        /// <value>
        /// The gray array.
        /// </value>
        public int[] GrayArray
        {
            get { return this.grayArray; }
            set { this.grayArray = value; }
        }

        /// <summary>
        /// Gets or sets the L.
        /// </summary>
        /// <value>
        /// The L.
        /// </value>
        public Line L
        {
            get { return this.l; }
            set { this.l = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [move circle1].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [move circle1]; otherwise, <c>false</c>.
        /// </value>
        public bool MoveCircle1
        {
            get { return this.moveCircle; }
            set { this.moveCircle = value; }
        }

        /// <summary>
        /// Gets or sets the curr circle.
        /// </summary>
        /// <value>
        /// The curr circle.
        /// </value>
        public int CurrCircle
        {
            get { return this.currCircle; }
            set { this.currCircle = value; }
        }

        /// <summary>
        /// Gets or sets the currl1.
        /// </summary>
        /// <value>
        /// The currl1.
        /// </value>
        public int Currl1
        {
            get { return this.currl1; }
            set { this.currl1 = value; }
        }

        /// <summary>
        /// Gets or sets the currl2.
        /// </summary>
        /// <value>
        /// The currl2.
        /// </value>
        public int Currl2
        {
            get { return this.currl2; }
            set { this.currl2 = value; }
        }

        /// <summary>
        /// Gets or sets the lines1.
        /// </summary>
        /// <value>
        /// The lines1.
        /// </value>
        public List<Line> Lines1
        {
            get { return this.lines; }
            set { this.lines = value; }
        }

        /// <summary>
        /// Gets or sets the circles1.
        /// </summary>
        /// <value>
        /// The circles1.
        /// </value>
        public List<Circle> Circles1
        {
            get { return this.circles; }
            set { this.circles = value; }
        }

        /// <summary>
        /// Handles the Paint event of the Pnl_Graph control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The event painting instance containing the event data.</param>
        private void Pnl_Graph_Paint(object sender, PaintEventArgs e)
        {
            foreach (Line ln in this.lines)
            {
                ln.Draw(e.Graphics);
            }

            foreach (Circle c in this.circles)
            {
                c.Draw2(e.Graphics);
            }
        }

        /// <summary>
        /// Handles the MouseDown event of the Pnl_Graph control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The event Mouse down instance containing the event data.</param>
        private void Pnl_Graph_MouseDown(object sender, MouseEventArgs e)
        {
            int onLine = this.IsOnline(e.X, e.Y);
            int onCircle = this.IsOnCircle(e.X, e.Y);

            if (onLine == -1 && onCircle == -1)
            {
                return;
            }

            if (onCircle != -1)
            {
                this.currCircle = onCircle;
                this.GetCurrentLines();

                if (ModifierKeys == Keys.Shift)
                {
                    this.RemoveCircle();
                    this.UpdateView();
                }
                else
                {
                    this.moveCircle = true;
                }
            }
            else
            {
                this.BreakTheCurrentLine(e.X, e.Y, onLine);
            }
        }

        /// <summary>
        /// Handles the MouseUp event of the Pnl_Graph control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The event mouse up instance containing the event data.</param>
        private void Pnl_Graph_MouseUp(object sender, MouseEventArgs e)
        {
            this.moveCircle = false;
            this.Pnl_Graph.Invalidate();
        }

        /// <summary>
        /// Handles the MouseMove event of the Pnl_Graph control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The event mouse move instance containing the event data.</param>
        private void Pnl_Graph_MouseMove(object sender, MouseEventArgs e)
        {
            if (this.moveCircle)
            {
                this.MoveCircleFunction(e.X, e.Y);
                this.UpdateView();
                
                // this.UpdateModifiedHistogram();
            }
        }

        /// <summary>
        /// Determines whether the specified x is on a line.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <returns>True if the mouse on a line, false otherwise</returns>
        private int IsOnline(int x, int y)
        {
            for (int i = 0; i < this.lines.Count; i++)
            {
                if (this.lines[i].IsTheBreakedLine(new Point(x, y), Tolerance))
                {
                    return i;
                }
            }

            return -1;
        }

        /// <summary>
        /// Determines whether [is on circle] [the specified x].
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <returns>True if the mouse on a circle, false otherwise</returns>
        private int IsOnCircle(int x, int y)
        {
            for (int i = 0; i < this.circles.Count; i++)
            {
                if (this.circles[i].PointInCircle2(x, y))
                {
                    return i;
                }
            }

            return -1;
        }

        /// <summary>
        /// Finds the x_ get Y.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <returns> y of the specified x </returns>
        private float FindX_GetY(float x)
        {
            foreach (Line ln in this.lines)
            {
                if (ln.Is_between(x, ln.P1X, ln.P2X, 0))
                {
                    float y = ln.GetY(x);
                    return y;
                }
            }

            return -1;
        }

        /// <summary>
        /// Moves the circle function.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        private void MoveCircleFunction(int x, int y)
        {
            if (x < 0)
            {
                x = 0;
            }
            else if (x > 255)
            {
                x = 255;
            }

            if (y < 0)
            {
                y = 0;
            }
            else if (y > 255)
            {
                y = 255;
            }

            this.circles[this.currCircle].PointX = x;
            this.circles[this.currCircle].PointY = y;
            this.lines[this.currl1].P1X = x;
            this.lines[this.currl1].P1Y = y;
            this.lines[this.currl2].P2X = x;
            this.lines[this.currl2].P2Y = y;
            this.Pnl_Graph.Invalidate();
        }

        /// <summary>
        /// Gets the current lines.
        /// </summary>
        private void GetCurrentLines()
        {
            for (int i = 0; i < this.lines.Count; i++)
            {
                if (this.lines[i].P1X == this.circles[this.currCircle].PointX && this.lines[i].P1Y == this.circles[this.currCircle].PointY)
                {
                    this.currl1 = i;
                }
                else if (this.lines[i].P2X == this.circles[this.currCircle].PointX && this.lines[i].P2Y == this.circles[this.currCircle].PointY)
                {
                    this.currl2 = i;
                }
            }
        }

        /// <summary>
        /// Breaks the current line.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="lineIndex">Index of the line.</param>
        private void BreakTheCurrentLine(int x, int y, int lineIndex)
        {
            Line l1 = new Line();
            Line l2 = new Line();
            this.lines[lineIndex].BreakLine(new Point(x, y), l1, l2);
            this.lines.RemoveAt(lineIndex);
            this.lines.Add(l1);
            this.lines.Add(l2);
            this.circles.Add(new Circle(x, y));
            this.Pnl_Graph.Invalidate();
        }

        /// <summary>
        /// Updates the gray array.
        /// </summary>
        private void UpdateGrayArray()
        {
            for (int i = 0; i < 256; i++)
            {
                this.grayArray[255 - i] = (int)this.FindX_GetY(i);
            }
        }

        /// <summary>
        /// Edits the original image.
        /// </summary>
        private void EditNewImage()
        {
            int height, width;
            if (this.resized)
            {
                height = this.Pcb_ModifiedImage.Size.Height;
                width = this.Pcb_ModifiedImage.Size.Width;
            }
            else
            {
                height = this.image.Height;
                width = this.image.Width;
            }

            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < width; j++)
                {
                    this.newImage[i, j].R = (byte)((byte)(this.grayArray[this.firstImage[i, j].R] - this.firstImage[i, j].R) + this.firstImage[i, j].R);
                    this.newImage[i, j].G = (byte)((byte)(this.grayArray[this.firstImage[i, j].G] - this.firstImage[i, j].G) + this.firstImage[i, j].G);
                    this.newImage[i, j].B = (byte)((byte)(this.grayArray[this.firstImage[i, j].B] - this.firstImage[i, j].B) + this.firstImage[i, j].B);
                }
            }

            this.Pcb_ModifiedImage.Image = Operations.DisplayMatrix(this.newImage);
        }

        /// <summary>
        /// Edits the original image.
        /// </summary>
        private void EditOriginalImage()
        {
            for (int i = 0; i < this.image.Height; i++)
            {
                for (int j = 0; j < this.image.Width; j++)
                {
                    // grayColor = (this.firstImage[i, j].R + this.firstImage[i, j].G + this.firstImage[i, j].B) / 3;
                    // this.image.ImageStructArray[i, j].R = (byte)((byte)(this.grayArray[grayColor] - grayColor) + this.firstImage[i, j].R);
                    // this.image.ImageStructArray[i, j].G = (byte)((byte)(this.grayArray[grayColor] - grayColor) + this.firstImage[i, j].G);
                    // this.image.ImageStructArray[i, j].B = (byte)((byte)(this.grayArray[grayColor] - grayColor) + this.firstImage[i, j].B);
                    this.image.ImageStructArray[i, j].R = (byte)((byte)(this.grayArray[this.firstOriginalImage[i, j].R] - this.firstOriginalImage[i, j].R) + this.firstOriginalImage[i, j].R);
                    this.image.ImageStructArray[i, j].G = (byte)((byte)(this.grayArray[this.firstOriginalImage[i, j].G] - this.firstOriginalImage[i, j].G) + this.firstOriginalImage[i, j].G);
                    this.image.ImageStructArray[i, j].B = (byte)((byte)(this.grayArray[this.firstOriginalImage[i, j].B] - this.firstOriginalImage[i, j].B) + this.firstOriginalImage[i, j].B);
                }
            }

            this.image.UpdateImage();
            this.Pcb_ModifiedImage.Image = this.image.ImageBitmap;
        }

        /// <summary>
        /// Inits the histograms.
        /// </summary>
        private void InitHistograms()
        {
            this.histo.FillGrayArray(this.image);
            for (int j = 0; j < 256; j++)
            {
                this.Chrt_Original.Series[0].Points.Add(new DataPoint(j, this.histo.Gray1[j]));
                this.Chrt_Modified.Series[0].Points.Add(new DataPoint(j, this.histo.Gray1[j]));
            }
        }

        /// <summary>
        /// Updates the modified histogram.
        /// </summary>
        private void UpdateModifiedHistogram()
        {
            this.histo.FillGrayArray(this.image);
            for (int j = 0; j < 256; j++)
            {
                this.Chrt_Modified.Series[0].Points.Add(new DataPoint(j, this.histo.Gray1[j]));
            }
        }

        /// <summary>
        /// Handles the Click event of the BtnOk control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void BtnOk_Click(object sender, EventArgs e)
        {
            this.ok = true;
            Main.OperationName = "Graph operations done.";
            this.EditOriginalImage();
            this.Close();
        }

        /// <summary>
        /// Handles the Click event of the BtnCancel control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void BtnCancel_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// Handles the FormClosing event of the PixelOperationsGraph control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.FormClosingEventArgs"/> instance containing the event data.</param>
        private void PixelOperationsGraph_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (!this.ok)
            {
                this.image.RetriveImageArray();
            }

            this.image.UpdateImage();
        }

        /// <summary>
        /// Handles the CheckedChanged event of the Chk_Preview control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Chk_Preview_CheckedChanged(object sender, EventArgs e)
        {
            if (this.Chk_Preview.Checked == false)
            {
                this.image.RetriveImageArray();
                this.image.UpdateImage();
                this.EditNewImage();
            }
            else
            {
                this.EditOriginalImage();
            }
        }

        /// <summary>
        /// Removes the circle.
        /// </summary>
        private void RemoveCircle()
        {
            Line l1 = this.lines[this.currl1];
            Line l2 = this.lines[this.currl2];
            this.lines.Remove(l1);
            this.lines.Remove(l2);
            this.lines.Add(Line.Join2lines(l1, l2));
            this.circles.RemoveAt(this.currCircle);
        }

        /// <summary>
        /// Updates the view.
        /// </summary>
        private void UpdateView()
        {
            this.UpdateGrayArray();

            if (this.Chk_Preview.Checked)
            {
                this.EditOriginalImage();
            }
            else
            {
                this.EditNewImage();
            }

            // this.UpdateModifiedHistogram();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            PixelOperationsAdvancedGraph AdvancedGraph = new PixelOperationsAdvancedGraph(image);
            AdvancedGraph.Show();
            this.Close();
        }
    }
}
